/******************************************************************************* * Copyright (c) 2006, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.window.IShellProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.internal.InternalSaveable; import org.eclipse.ui.internal.PartSite; import org.eclipse.ui.progress.IJobRunnable; /** * A <code>Saveable</code> represents a unit of saveability, e.g. an editable * subset of the underlying domain model that may contain unsaved changes. * Different workbench parts (editors and views) may present the same saveables * in different ways. This interface allows the workbench to provide more * appropriate handling of operations such as saving and closing workbench * parts. For example, if two editors sharing the same saveable with unsaved * changes are closed simultaneously, the user is only prompted to save the * changes once for the shared saveable, rather than once for each editor. * <p> * Workbench parts that work in terms of saveables should implement * {@link ISaveablesSource}. * </p> * * @see ISaveablesSource * @since 3.2 */ public abstract class Saveable extends InternalSaveable implements IAdaptable { /** * Either {@code null} or the System's {@link SWT#CURSOR_WAIT} cursor * instance. Should never be disposed. */ private Cursor waitCursor; private Cursor originalCursor; /** * Attempts to show this saveable in the given page and returns * <code>true</code> on success. The default implementation does nothing * and returns <code>false</code>. * * @param page * the workbench page in which to show this saveable * @return <code>true</code> if this saveable is now visible to the user * @since 3.3 */ public boolean show(IWorkbenchPage page) { if (page == null) { // I wish it was easier to avoid warnings about unused parameters } return false; } /** * Returns the name of this saveable for display purposes. * * @return the model's name; never <code>null</code>. */ public abstract String getName(); /** * Returns the tool tip text for this saveable. This text is used to * differentiate between two inputs with the same name. For instance, * MyClass.java in folder X and MyClass.java in folder Y. The format of the * text varies between input types. * * @return the tool tip text; never <code>null</code> */ public abstract String getToolTipText(); /** * Returns the image descriptor for this saveable. * * @return the image descriptor for this model; may be <code>null</code> * if there is no image */ public abstract ImageDescriptor getImageDescriptor(); /** * Saves the contents of this saveable. * <p> * If the save is cancelled through user action, or for any other reason, * the part should invoke <code>setCancelled</code> on the * <code>IProgressMonitor</code> to inform the caller. * </p> * <p> * This method is long-running; progress and cancellation are provided by * the given progress monitor. * </p> * * @param monitor * the progress monitor * @throws CoreException * if the save fails; it is the caller's responsibility to * report the failure to the user */ public abstract void doSave(IProgressMonitor monitor) throws CoreException; /** * Returns whether the contents of this saveable have changed since the last * save operation. * <p> * <b>Note:</b> this method is called frequently, for example by actions to * determine their enabled status. * </p> * * @return <code>true</code> if the contents have been modified and need * saving, and <code>false</code> if they have not changed since * the last save */ public abstract boolean isDirty(); /** * Clients must implement equals and hashCode as defined in * {@link Object#equals(Object)} and {@link Object#hashCode()}. Two * saveables should be equal if their dirty state is shared, and saving one * will save the other. If two saveables are equal, their names, tooltips, * and images should be the same because only one of them will be shown when * prompting the user to save. * * @param object * @return true if this Saveable is equal to the given object */ @Override public abstract boolean equals(Object object); /** * Clients must implement equals and hashCode as defined in * {@link Object#equals(Object)} and {@link Object#hashCode()}. Two * saveables should be equal if their dirty state is shared, and saving one * will save the other. If two saveables are equal, their hash codes MUST be * the same, and their names, tooltips, and images should be the same * because only one of them will be shown when prompting the user to save. * <p> * IMPORTANT: Implementers should ensure that the hashCode returned is * sufficiently unique so as not to collide with hashCodes returned by other * implementations. It is suggested that the defining plug-in's ID be used * as part of the returned hashCode, as in the following example: * </p> * * <pre> * int PRIME = 31; * int hash = ...; // compute the "normal" hash code, e.g. based on some identifier unique within the defining plug-in * return hash * PRIME + MY_PLUGIN_ID.hashCode(); * </pre> * * @return a hash code */ @Override public abstract int hashCode(); /** * Saves this saveable, or prepares this saveable for a background save * operation. Returns null if this saveable has been successfully saved, or * a job runnable that needs to be run to complete the save in the * background. This method is called in the UI thread. If this saveable * supports saving in the background, it should do only minimal work. * However, since the job runnable returned by this method (if any) will not * run on the UI thread, this method should copy any state that can only be * accessed from the UI thread so that the job runnable will be able to * access it. * <p> * The supplied shell provider can be used from within this method and from * within the job runnable for the purpose of parenting dialogs. Care should * be taken not to open dialogs gratuitously and only if user input is * required for cases where the save cannot otherwise proceed - note that in * any given save operation, many saveable objects may be saved at the same * time. In particular, errors should be signaled by throwing an exception, * or if an error occurs while running the job runnable, an error status * should be returned. * </p> * <p> * If the foreground part of the save is cancelled through user action, or * for any other reason, the part should invoke <code>setCancelled</code> * on the <code>IProgressMonitor</code> to inform the caller. If the * background part of the save is cancelled, the job should return a * {@link IStatus#CANCEL} status. * </p> * <p> * This method is long-running; progress and cancellation are provided by * the given progress monitor. * </p> * <p> * The default implementation of this method calls * {@link #doSave(IProgressMonitor)} and returns <code>null</code>. * </p> * * @param monitor * a progress monitor used for reporting progress and * cancellation * @param shellProvider * an object that can provide a shell for parenting dialogs * @return <code>null</code> if this saveable has been saved successfully, * or a job runnable that needs to be run to complete the save in * the background. * @throws CoreException * if the save fails; it is the caller's responsibility to * report the failure to the user * @since 3.3 */ public IJobRunnable doSave(IProgressMonitor monitor, IShellProvider shellProvider) throws CoreException { doSave(monitor); return null; } /** * Disables the UI of the given parts containing this saveable if necessary. * This method is not intended to be called by clients. A corresponding call * to * <p> * Saveables that can be saved in the background should ensure that the user * cannot make changes to their data from the UI, for example by disabling * controls, unless they are prepared to handle this case. This method is * called on the UI thread after a job runnable has been returned from * {@link #doSave(IProgressMonitor, IShellProvider)} and before * spinning the event loop. The <code>closing</code> flag indicates that * this saveable is currently being saved in response to closing a workbench * part, in which case further changes to this saveable through the UI must * be prevented. * </p> * <p> * The default implementation calls setEnabled(false) on the given parts' * composites. * </p> * * @param parts * the workbench parts containing this saveable * @param closing * a boolean flag indicating whether the save was triggered by a * request to close a workbench part, and all of the given parts * will be closed after the save operation finishes successfully. * * @since 3.3 */ public void disableUI(IWorkbenchPart[] parts, boolean closing) { for (IWorkbenchPart workbenchPart : parts) { Composite paneComposite = (Composite) ((PartSite) workbenchPart.getSite()).getModel().getWidget(); Control[] paneChildren = paneComposite.getChildren(); Composite toDisable = ((Composite) paneChildren[0]); toDisable.setEnabled(false); if (waitCursor == null) { waitCursor = workbenchPart.getSite().getWorkbenchWindow().getShell().getDisplay() .getSystemCursor(SWT.CURSOR_WAIT); } if (waitCursor.equals(paneComposite.getCursor())) { originalCursor = paneComposite.getCursor(); paneComposite.setCursor(waitCursor); } } } /** * Enables the UI of the given parts containing this saveable after a * background save operation has finished. This method is not intended to be * called by clients. * <p> * The default implementation calls setEnabled(true) on the given parts' * composites. * </p> * * @param parts * the workbench parts containing this saveable * * @since 3.3 */ public void enableUI(IWorkbenchPart[] parts) { for (IWorkbenchPart workbenchPart : parts) { Composite paneComposite = (Composite) ((PartSite) workbenchPart.getSite()).getModel().getWidget(); Control[] paneChildren = paneComposite.getChildren(); Composite toEnable = ((Composite) paneChildren[0]); paneComposite.setCursor(originalCursor); if (waitCursor != null) { /* * waitCursor is always the System SWT.CURSOR_WAIT instance and * should never be disposed */ waitCursor = null; } toEnable.setEnabled(true); } } /** * This implementation of {@link IAdaptable#getAdapter(Class)} returns * <code>null</code>. Subclasses may override. This allows two unrelated * subclasses of Saveable to implement {@link #equals(Object)} and * {@link #hashCode()} based on an underlying implementation class that is * shared by both Saveable subclasses. * * @since 3.3 */ @Override public <T> T getAdapter(Class<T> adapter) { return null; } }